/*
Protein:  brief Definition of Protein aspects for Structure class.
    Copyright (C) 2010  Jorge E. A. Atala
                        FuDePAN
    This file is part of the Biopp Proyect.

    Backbones-selector is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Backbones-selector is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Backbones-selector.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PROTEIN_H
#define PROTEIN_H

#include <cmath>
#include <string>
#include "backbones-selector/common/energyTable.h"
#define NO_BIO_MOLECULAR
#define NO_RNA_STRUCTURE
#include <biopp/biopp.h>
using namespace biopp;

namespace backbonesSelector
{


// forward these

template<class A>
class Protein_Aspect;

template<class A, class Energy = float>
class EnergyProtein_Aspect;

typedef Protein_Aspect<Structure< > > Protein;
typedef EnergyProtein_Aspect<Protein> ProteinWithEnergy;
typedef Coord3d AlphaCarbonPosition;

template<class A>
class Protein_Aspect : public A
{
protected:

    AminoacidSequence aminoacids;

public:

    Protein_Aspect(const Structure< >& structure)
        :   A(structure)
    {
    }

    Protein_Aspect(const Structure< >& structure, const AminoacidSequence& sequence)
        :   A(structure),
            aminoacids(sequence)
    {
    }

    Protein_Aspect(const std::vector<AlphaCarbonPosition>& vector)
        :   A(vector)
    {
    }

    Protein_Aspect(const AminoacidSequence& sequence)
        :   A(),
            aminoacids(sequence)
    {
    }


    Protein_Aspect(const std::vector<AlphaCarbonPosition>& vector, const AminoacidSequence& sequence)
        :   A(vector),
            aminoacids(sequence)
    {
    }

    inline void setAminoacids(const AminoacidSequence& sequence)
    {
        aminoacids = sequence;
    }

    inline void getAminoacidsSequence(AminoacidSequence& output) const
    {
        output = aminoacids;
    }
};


template<class A, class Energy>
class EnergyProtein_Aspect : public A
{
private:
    typedef typename AlphaCarbonPosition::Coord Distance;

protected:
    Energy proteinEnergy;

public:

    EnergyProtein_Aspect(const Structure< >& structure)
        :   A(structure)
    {
    }

    EnergyProtein_Aspect(const Structure< >& structure, const AminoacidSequence& sequence)
        :   A(structure, sequence)
    {
    }

    EnergyProtein_Aspect(const std::vector<AlphaCarbonPosition>& vector)
        :   A(vector)
    {
    }

    EnergyProtein_Aspect(const std::vector<AlphaCarbonPosition>& vector, const AminoacidSequence& sequence)
        :   A(vector, sequence)
    {
    }

    EnergyProtein_Aspect(const AminoacidSequence& sequence)
        :   A(sequence)
    {
    }

    void setEnergy(const EnergyTable<Energy, Distance >& energyTable)
    {

        const int maxDistance = energyTable.getMaxDistance();
        const unsigned int squareMaxDistance =  mili::square(maxDistance);
        Energy structureEnergy = 0;
        typedef std::vector<AlphaCarbonPosition >::iterator AlphaCarbonIterator;
        AlphaCarbonIterator itFirstAlphaCarbon = A::_item_vector.begin();
        AlphaCarbonIterator endOfAlphaCarbons = A::_item_vector.end();
        for (; itFirstAlphaCarbon < endOfAlphaCarbons - 1 ; ++itFirstAlphaCarbon)
        {
            AlphaCarbonIterator itSecondAlphaCarbon = itFirstAlphaCarbon + 1;
            for (; itSecondAlphaCarbon < endOfAlphaCarbons; ++itSecondAlphaCarbon)
            {
                if ((abs(itFirstAlphaCarbon->xDistance(*itSecondAlphaCarbon)) < maxDistance)
                        and (abs(itFirstAlphaCarbon->yDistance(*itSecondAlphaCarbon)) < maxDistance)
                        and (abs(itFirstAlphaCarbon->zDistance(*itSecondAlphaCarbon)) < maxDistance))
                {
                    const Distance alphaCarbonsSquareDistance = itFirstAlphaCarbon->squareDistance(*itSecondAlphaCarbon);

                    if (alphaCarbonsSquareDistance < squareMaxDistance)
                    {
                        structureEnergy += energyTable.getEnergy(
                                               A::aminoacids[itFirstAlphaCarbon - A::_item_vector.begin()],
                                               A::aminoacids[itSecondAlphaCarbon - A::_item_vector.begin()],
                                               sqrt(alphaCarbonsSquareDistance));
                    }
                }
            }
        }
        this->proteinEnergy = structureEnergy;
    }

    bool operator<(const EnergyProtein_Aspect& other) const
    {
        //The energy is negative, then if the energy value is less than the other protein,
        //this protein haves more energy
        return proteinEnergy < other.proteinEnergy;
    }

    inline Energy getEnergy()const
    {
        return proteinEnergy;
    }

};

struct ProteinRanking
{
    bool operator()(const ProteinWithEnergy* const protein1, const ProteinWithEnergy* const protein2) const
    {
        return *protein1 < *protein2;
    }
};

}

#endif
